import cv2
import mediapipe as mp
import math
import serial
import tkinter as tk
from tkinter import messagebox, ttk
from PIL import Image, ImageTk
import random
import time

# --- 手部关键点常量 ---
WRIST = 0
THUMB_CMC = 1
THUMB_MCP = 2
THUMB_IP = 3
THUMB_TIP = 4
INDEX_FINGER_MCP = 5
INDEX_FINGER_PIP = 6
INDEX_FINGER_DIP = 7
INDEX_FINGER_TIP = 8
MIDDLE_FINGER_MCP = 9
MIDDLE_FINGER_PIP = 10
MIDDLE_FINGER_DIP = 11
MIDDLE_FINGER_TIP = 12
RING_FINGER_MCP = 13
RING_FINGER_PIP = 14
RING_FINGER_DIP = 15
RING_FINGER_TIP = 16
PINKY_MCP = 17
PINKY_PIP = 18
PINKY_DIP = 19
PINKY_TIP = 20


# --- 辅助函数 ---

def vector_2d_angle(v1, v2):
    """ 计算两个2D向量之间的角度 """
    v1_x, v1_y = v1
    v2_x, v2_y = v2
    try:
        angle_ = math.degrees(math.acos(
            (v1_x * v2_x + v1_y * v2_y) / (((v1_x ** 2 + v1_y ** 2) ** 0.5) * ((v2_x ** 2 + v2_y ** 2) ** 0.5))))
    except (ZeroDivisionError, ValueError):
        angle_ = 65535.
    return 180. if angle_ > 180. else angle_


def hand_angle(hand_):
    """ 计算各手指角度 """
    angle_list = []
    points_indices = [
        THUMB_CMC, THUMB_MCP, THUMB_IP, THUMB_TIP, WRIST, INDEX_FINGER_MCP, INDEX_FINGER_PIP, INDEX_FINGER_TIP,
        MIDDLE_FINGER_MCP, MIDDLE_FINGER_PIP, MIDDLE_FINGER_TIP, RING_FINGER_MCP, RING_FINGER_PIP, RING_FINGER_TIP,
        PINKY_MCP, PINKY_PIP, PINKY_TIP
    ]
    p = [(int(hand_[i][0]), int(hand_[i][1])) for i in points_indices]

    vectors = [
        ((p[0][0] - p[1][0], p[0][1] - p[1][1]), (p[2][0] - p[3][0], p[2][1] - p[3][1])),  # Thumb
        ((p[4][0] - p[5][0], p[4][1] - p[5][1]), (p[6][0] - p[7][0], p[6][1] - p[7][1])),  # Index
        ((p[4][0] - p[8][0], p[4][1] - p[8][1]), (p[9][0] - p[10][0], p[9][1] - p[10][1])),  # Middle
        ((p[4][0] - p[11][0], p[4][1] - p[11][1]), (p[12][0] - p[13][0], p[12][1] - p[13][1])),  # Ring
        ((p[4][0] - p[14][0], p[4][1] - p[14][1]), (p[15][0] - p[16][0], p[15][1] - p[16][1]))  # Pinky
    ]
    for v1, v2 in vectors:
        angle_list.append(vector_2d_angle(v1, v2))
    return angle_list


def get_distance(p1, p2):
    """ 计算两点之间的欧几里得距离 """
    return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)


def h_gesture(angle_list, hand_landmarks_local):
    """ 识别手势的核心函数，包含所有优化逻辑 """
    thr_angle_straight, thr_angle_bent = 45.0, 70.0
    palm_width = get_distance(hand_landmarks_local[INDEX_FINGER_MCP], hand_landmarks_local[PINKY_MCP])

    thumb_tip, index_tip = hand_landmarks_local[THUMB_TIP], hand_landmarks_local[INDEX_FINGER_TIP]

    # 特殊手势优先判断
    if get_distance(thumb_tip, index_tip) < palm_width * 0.4 and \
            all(angle_list[i] < thr_angle_straight for i in [2, 3, 4]) and angle_list[1] > thr_angle_bent:
        return 'O'

    thumb_is_vertical = hand_landmarks_local[THUMB_TIP][1] < hand_landmarks_local[THUMB_IP][1] < \
                        hand_landmarks_local[THUMB_MCP][1]
    thumb_is_away = get_distance(thumb_tip, hand_landmarks_local[INDEX_FINGER_PIP]) > palm_width * 0.9
    if thumb_is_vertical and thumb_is_away and all(angle_list[i] > thr_angle_bent for i in [1, 2, 3, 4]):
        return 'T'

    if angle_list[1] < thr_angle_straight and angle_list[4] < thr_angle_straight and \
            all(angle_list[i] > thr_angle_bent for i in [0, 2, 3]):
        return 'R'

    # 数字手势
    ext_thresh, bent_thresh = 49.0, 65.0

    four_fingers_bent = all(angle_list[i] > bent_thresh for i in [1, 2, 3, 4])
    thumb_is_closed = get_distance(thumb_tip, hand_landmarks_local[INDEX_FINGER_PIP]) < palm_width * 0.5
    if four_fingers_bent and thumb_is_closed:
        return "0"

    rules = {
        "5": (lambda al: all(al[i] < ext_thresh for i in range(5))),
        "8": (lambda al: all(al[i] < ext_thresh for i in [0, 1]) and all(al[i] > bent_thresh for i in [2, 3, 4])),
        "1": (lambda al: al[1] < ext_thresh and all(al[i] > bent_thresh for i in [0, 2, 3, 4])),
        "6": (lambda al: all(al[i] < ext_thresh for i in [0, 4]) and all(al[i] > bent_thresh for i in [1, 2, 3])),
        "3": (lambda al: all(al[i] < ext_thresh for i in [1, 2, 3]) and all(al[i] > bent_thresh for i in [0, 4])),
        "2": (lambda al: all(al[i] < ext_thresh for i in [1, 2]) and all(al[i] > bent_thresh for i in [0, 3, 4])),
        "4": (lambda al: al[0] > bent_thresh and all(al[i] < ext_thresh for i in [1, 2, 3, 4]))
    }
    for gesture, rule in rules.items():
        if rule(angle_list):
            return gesture
    return None


# --- 主应用类 ---

class GestureApp:
    def __init__(self, root_window):
        self.root = root_window
        self.root.title("手势识别控制 vFinal")

        # 应用状态变量
        self.is_running = False
        self.cap = None
        self.ser = None
        self.send_serial_on_gesture = True
        self.last_player_gesture = None
        self.last_computer_move_text = "--"
        self.last_game_result_text = "--"
        self.last_send_time = 0

        # 初始化 MediaPipe
        self.mp_drawing = mp.solutions.drawing_utils
        self.mp_hands = mp.solutions.hands
        self.hands = self.mp_hands.Hands(
            static_image_mode=False, max_num_hands=1,
            min_detection_confidence=0.7, min_tracking_confidence=0.7
        )

        # --- GUI 界面设置 ---
        self.main_frame = ttk.Frame(self.root, padding="10")
        self.main_frame.pack(expand=True, fill=tk.BOTH)

        self.controls_frame = ttk.LabelFrame(self.main_frame, text="控制面板", padding="10")
        self.controls_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10, pady=10)

        # 常规设置
        ttk.Label(self.controls_frame, text="摄像头编号:").grid(row=0, column=0, sticky="w", padx=5, pady=3)
        self.camera_no_entry = ttk.Entry(self.controls_frame, width=12)
        self.camera_no_entry.insert(0, "0")
        self.camera_no_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=3)

        self.send_serial_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(self.controls_frame, text="发送串口数据", variable=self.send_serial_var).grid(row=1, column=0,
                                                                                                      columnspan=2,
                                                                                                      sticky="w",
                                                                                                      padx=5, pady=3)

        ttk.Label(self.controls_frame, text="串口设备号:").grid(row=2, column=0, sticky="w", padx=5, pady=3)
        self.com_port_entry = ttk.Entry(self.controls_frame, width=12)
        self.com_port_entry.insert(0, "COM5")
        self.com_port_entry.grid(row=2, column=1, sticky="ew", padx=5, pady=3)

        ttk.Label(self.controls_frame, text="波特率:").grid(row=3, column=0, sticky="w", padx=5, pady=3)
        self.baud_rates_entry = ttk.Entry(self.controls_frame, width=12)
        self.baud_rates_entry.insert(0, "115200")
        self.baud_rates_entry.grid(row=3, column=1, sticky="ew", padx=5, pady=3)

        # 猜拳模式GUI
        self.rps_mode_var = tk.BooleanVar(value=False)
        self.rps_mode_check = ttk.Checkbutton(self.controls_frame, text="开启猜拳模式 ✊✌️✋", variable=self.rps_mode_var,
                                              command=self.toggle_rps_options)
        self.rps_mode_check.grid(row=4, column=0, columnspan=2, sticky="w", padx=5, pady=5)

        self.difficulty_frame = ttk.Frame(self.controls_frame)
        self.difficulty_frame.grid(row=5, column=0, columnspan=2, sticky='w', padx=20)
        self.rps_difficulty_var = tk.StringVar(value="standard")
        self.standard_mode_radio = ttk.Radiobutton(self.difficulty_frame, text="标准模式",
                                                   variable=self.rps_difficulty_var, value="standard")
        self.standard_mode_radio.pack(side=tk.LEFT)
        self.hard_mode_radio = ttk.Radiobutton(self.difficulty_frame, text="困难模式", variable=self.rps_difficulty_var,
                                               value="hard")
        self.hard_mode_radio.pack(side=tk.LEFT, padx=10)

        self.interval_frame = ttk.Frame(self.controls_frame)
        self.interval_frame.grid(row=6, column=0, columnspan=2, sticky='w', padx=20, pady=(0, 5))
        ttk.Label(self.interval_frame, text="发送间隔(秒):").pack(side=tk.LEFT)
        self.send_interval_entry = ttk.Entry(self.interval_frame, width=6)
        self.send_interval_entry.insert(0, "2")
        self.send_interval_entry.pack(side=tk.LEFT)

        # 按钮和状态显示
        self.start_button = ttk.Button(self.controls_frame, text="启动", command=self.toggle_detection)
        self.start_button.grid(row=7, column=0, columnspan=2, pady=10)

        self.quit_button = ttk.Button(self.controls_frame, text="退出", command=self.quit_app)
        self.quit_button.grid(row=8, column=0, columnspan=2, pady=5)

        ttk.Separator(self.controls_frame, orient='horizontal').grid(row=9, column=0, columnspan=2, sticky='ew',
                                                                     pady=10)

        self.gesture_status_label = ttk.Label(self.controls_frame, text="你的手势: N/A", font=("Arial", 12))
        self.gesture_status_label.grid(row=10, column=0, columnspan=2, pady=5)

        self.computer_move_label = ttk.Label(self.controls_frame, text="电脑出招: --", font=("Arial", 12, "bold"))
        self.computer_move_label.grid(row=11, column=0, columnspan=2, pady=5)

        self.game_result_label = ttk.Label(self.controls_frame, text="结果: --", font=("Arial", 14, "bold"),
                                           foreground="blue")
        self.game_result_label.grid(row=12, column=0, columnspan=2, pady=5)

        # 视频画面框架
        self.video_frame = ttk.LabelFrame(self.main_frame, text="摄像头画面", padding="10")
        self.video_frame.pack(side=tk.RIGHT, expand=True, fill=tk.BOTH, padx=10, pady=10)
        self.canvas = tk.Canvas(self.video_frame, width=640, height=480, bg="lightgray")
        self.canvas.pack(expand=True, fill=tk.BOTH)

        self.toggle_rps_options()

    def toggle_rps_options(self):
        """ 根据主复选框状态，启用或禁用难度选项 """
        state = tk.NORMAL if self.rps_mode_var.get() else tk.DISABLED
        self.standard_mode_radio.config(state=state)
        self.hard_mode_radio.config(state=state)
        self.send_interval_entry.config(state=state)

    def toggle_detection(self):
        """ 启动或终止检测 """
        if not self.is_running:
            try:
                camera_no = int(self.camera_no_entry.get())
                com_port = self.com_port_entry.get()
                baud_rates = int(self.baud_rates_entry.get())
            except ValueError:
                messagebox.showerror("输入错误", "摄像头编号和波特率必须是有效的整数！")
                return

            try:
                self.cap = cv2.VideoCapture(camera_no, cv2.CAP_DSHOW)
                if self.cap.isOpened():
                    self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
                    self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
                else:
                    messagebox.showerror("摄像头错误", f"无法打开摄像头 {camera_no}！")
                    self.cap = None
                    return
            except Exception as e:
                messagebox.showerror("摄像头错误", f"摄像头初始化失败：{str(e)}")
                self.cap = None
                return

            if self.send_serial_var.get():
                try:
                    self.ser = serial.Serial(com_port, baud_rates, timeout=1)
                except Exception as e:
                    messagebox.showerror("串口错误", f"串口 {com_port} 初始化失败：{str(e)}")
                    self.ser = None

            self.is_running = True
            self.start_button.config(text="终止")
            self.rps_mode_check.config(state=tk.DISABLED)
            self.toggle_rps_options()
            self.last_send_time = 0
            self.update_frame()
        else:
            self.stop_detection()
            self.start_button.config(text="启动")
            self.rps_mode_check.config(state=tk.NORMAL)
            self.toggle_rps_options()

    def update_frame(self):
        """ 主循环，用于捕获和处理每一帧 """
        if not self.is_running:
            return

        ret, frame = self.cap.read()
        if not ret:
            self.root.after(20, self.update_frame)
            return

        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        frame_rgb = cv2.flip(frame_rgb, 1)
        results = self.hands.process(frame_rgb)
        frame_bgr = cv2.flip(frame, 1)

        player_gesture_code = None
        if results.multi_hand_landmarks:
            for hand_landmarks in results.multi_hand_landmarks:
                self.mp_drawing.draw_landmarks(frame_bgr, hand_landmarks, self.mp_hands.HAND_CONNECTIONS)
                hand_local = [(int(lm.x * frame_bgr.shape[1]), int(lm.y * frame_bgr.shape[0])) for lm in
                              hand_landmarks.landmark]
                if hand_local:
                    player_gesture_code = h_gesture(hand_angle(hand_local), hand_local)

        # 核心逻辑: 状态管理与串口通信
        if player_gesture_code != self.last_player_gesture:
            string_to_send = ""
            if self.rps_mode_var.get():
                try:
                    interval = float(self.send_interval_entry.get())
                except ValueError:
                    interval = 2.0

                current_time = time.time()
                if (current_time - self.last_send_time) > interval:
                    rps_map = {'0': '石头 ✊', '2': '剪刀 ✌️', '5': '布 ✋'}
                    if player_gesture_code in rps_map:
                        difficulty = self.rps_difficulty_var.get()
                        result_code = ''

                        if difficulty == "hard":
                            if player_gesture_code == '0':
                                computer_action_code = '5'
                            elif player_gesture_code == '2':
                                computer_action_code = '0'
                            else:
                                computer_action_code = '2'
                            self.last_game_result_text = "你输了! 😞"
                            result_code = 'L'
                        else:  # 标准模式
                            computer_action_code = random.choice(list(rps_map.keys()))
                            if player_gesture_code == computer_action_code:
                                self.last_game_result_text = "平局! 🤝"
                                result_code = 'D'
                            elif (player_gesture_code, computer_action_code) in [('0', '2'), ('2', '5'), ('5', '0')]:
                                self.last_game_result_text = "你赢了! 🎉"
                                result_code = 'W'
                            else:
                                self.last_game_result_text = "你输了! 😞"
                                result_code = 'F'

                        string_to_send = f"{computer_action_code}{result_code}"
                        self.last_computer_move_text = rps_map[computer_action_code]
                        self.last_send_time = current_time
                    else:
                        self.last_computer_move_text = "--"
                        self.last_game_result_text = "--"
            else:
                if player_gesture_code:
                    string_to_send = player_gesture_code

            if string_to_send and self.send_serial_var.get() and self.ser and self.ser.is_open:
                final_payload = f"{string_to_send}"
                try:
                    self.ser.write(final_payload.encode('utf-8'))
                    print(f"发送: {final_payload}")
                except Exception as e:
                    print(f"串口发送错误: {e}")

            self.last_player_gesture = player_gesture_code

        # GUI和屏幕显示更新
        player_display_text_gui = "N/A"
        if player_gesture_code:
            if self.rps_mode_var.get():
                rps_map_display = {'0': '石头 ✊', '2': '剪刀 ✌️', '5': '布 ✋'}
                player_display_text_gui = rps_map_display.get(player_gesture_code, "无效出拳")
            else:
                special_map_display = {'O': 'OK', 'T': 'ThumbsUp', 'R': 'Rock'}
                player_display_text_gui = special_map_display.get(player_gesture_code, player_gesture_code)

        self.gesture_status_label.config(text=f"你的手势: {player_display_text_gui}")
        self.computer_move_label.config(text=f"电脑出招: {self.last_computer_move_text}")
        self.game_result_label.config(text=f"结果: {self.last_game_result_text}")

        display_text_video = player_gesture_code if player_gesture_code else "N/A"
        cv2.putText(frame_bgr, display_text_video, (30, 80), cv2.FONT_HERSHEY_SIMPLEX, 2.5, (0, 0, 255), 4, cv2.LINE_AA)

        # 将图像显示在Canvas上
        frame_display_rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
        img = Image.fromarray(frame_display_rgb)
        canvas_width, canvas_height = self.canvas.winfo_width(), self.canvas.winfo_height()
        if canvas_width > 1 and canvas_height > 1:
            img_width, img_height = img.size
            aspect_ratio = img_width / img_height
            if canvas_width / canvas_height > aspect_ratio:
                new_height, new_width = canvas_height, int(aspect_ratio * canvas_height)
            else:
                new_width, new_height = canvas_width, int(canvas_width / aspect_ratio)
            img = img.resize((new_width, new_height), Image.LANCZOS)
        self.photo = ImageTk.PhotoImage(img)
        self.canvas.create_image(canvas_width // 2, canvas_height // 2, anchor="center", image=self.photo)

        self.root.after(20, self.update_frame)

    def stop_detection(self):
        """ 停止检测并清理资源 """
        self.is_running = False
        self.last_player_gesture = None
        self.last_computer_move_text = "--"
        self.last_game_result_text = "--"
        self.last_send_time = 0
        if self.cap:
            self.cap.release()
            self.cap = None
        if self.ser and self.ser.is_open:
            self.ser.close()
            self.ser = None
        self.canvas.delete("all")
        self.computer_move_label.config(text="电脑出招: --")
        self.game_result_label.config(text="结果: --")

    def quit_app(self):
        """ 退出应用 """
        self.stop_detection()
        self.root.quit()
        self.root.destroy()


if __name__ == '__main__':
    root = tk.Tk()
    app = GestureApp(root)
    root.minsize(800, 720)
    root.protocol("WM_DELETE_WINDOW", app.quit_app)
    root.mainloop()